#!/usr/bin/env python
#
# Copyright 2008 Guillaume Bort (http://guillaume.bort.fr)
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

"""play.py for google app engine

play.py is an extension module for the WSGI-compatible web application framework provided by Google App Engine.
It provides some nice features that I needed in order to move my applications to App Engine :

    * URL patterns with named arguments and HTTP method association (routes).
    * Controller/Action pattern instead of the simpler Handler pattern.
    * Before/After action interceptor (very usefull for authentication).
    * Result methods which break the control flow.
    * Session & Flash scopes support with secure cookie based storage.
    * Better errors report.

Check the reworked 'Getting Started' at http://code.google.com/p/play-py/
"""


import logging
import base64
import hashlib
import hmac
import re
import urllib
import urlparse
import sys
import os
import traceback
import functools
import wsgiref.handlers

from paste.util.mimeparse import best_match, appengine
import formencode
from UserDict import UserDict

from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from google.appengine.ext.db import djangoforms

# PUT A VERY SECRET KEY HERE
SECRET_KEY = '''1d5eh91wxueg5ns4t8'''




# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Redirect

class Redirect(Exception):
    def __init__(self, to, params=None, **kw):
        self.to = to
        self.params = params
        self.kw=kw

    def do(self, controller):
        uri = None
        if not self.params:
            self.params={}
        if callable(self.to):
            uri = controller.router.reverse_route(self.to, self.kw if self.kw else {})
        else:
            uri = self.to
        if not self.params.has_key('redirect_to'):
            self.params['redirect_to']=controller.request.uri
        params='?'+urllib.urlencode(self.params)
        controller.response.set_status(302)
        absolute_url = urlparse.urljoin(controller.request.application_url, uri)
        controller.response.headers['Location'] = str(absolute_url)+params
        controller.response.clear()

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Application

class Application:

    def __init__(self, routes):
        self.router = Router(routes)
        wsgiref.handlers.CGIHandler().run(self)

    def __call__(self, environ, start_response):
        request = Request(environ)
        response = Response()

        (controller, action) = self.router.route(request)

        if controller and action:
            controller.request, controller.response = request, response
            controller.session = Session(controller)
            controller.flash = Flash(controller)
            controller.params = Params(controller)
            controller.render_args = {}

            # Retrieve args
            argnames = action.im_func.func_code.co_varnames[1:action.im_func.func_code.co_argcount]
            args = {}
            for argname in argnames:
                args[argname] = controller.params[argname]

            # OK, call the action (with before and after)
            try:
                controller._veryBefore()
                remainder=[]
                try:
                    deco=Decoration.get_decoration(action)
                    if hasattr(controller, '_before'):
                        controller._before()
                    deco.run_hooks('before_validate', remainder, args) 
                    try: 
                        #formencode.api.Invalid
                        args = self._perform_validate(action, args)
                        deco.run_hooks('before_call', remainder, args)
                        output = action(controller, **args) or dict()

                    except formencode.api.Invalid, inv:
                       controller, output = self._handle_validation_errors(action,  remainder, params, inv)

                    if isinstance(output, dict):
                        deco.run_hooks('before_render', remainder, args, output)
                        content_type, engine_name, tmplt, exclude_names = deco.lookup_template_engine(controller.request)
                        ####engine=lookup_engine(engine_name)
                        tmplt=getattr(controller, '_use_template', tmplt) #override template
                        output.update({
                            'session': controller.session,
                            'flash': controller.flash
                        })
                        ##?response = #engine.call()
                        ##response = self._render_response(action, output)
                        controller.response.out.write(template.render('views/%s' % tmplt, output))
                        deco.run_hooks('after_render', response)
                        ##return response
                        
                    elif isinstance(output, str):
                        controller.response.out.write(output)
                except Redirect, e:
                    e.do(controller)
                if hasattr(controller, '_after'): controller._after()
                controller._veryAfter()
            except:
                (type, value, tb) = sys.exc_info()
                logging.error(''.join(traceback.format_exception(*sys.exc_info())))
                report_error(type, value, tb, response)

        else:
            response.set_status(404)

        response.wsgi_write(start_response)
        return ['']


    def _perform_validate(self, action, params):
        """
        Validation is stored on the "validation" attribute of the controller's
        decoration.

        It can be in three forms:

        ##1) A dictionary, with key being the request parameter name, and value a
        ##   FormEncode validator.

        ##2) A FormEncode Schema object

        3) Any object with a "validate" method that takes a dictionary of the
           request variables.
           
        4) A Django Form
        
        NOTE!!! only Django Form is implemented for now

        Validation can "clean" or otherwise modify the parameters that were
        passed in, not just raise an exception.  Validation exceptions should
        be FormEncode Invalid objets.
        """

        validation = getattr(action.decoration, 'validation', None)
        if validation is None:
            return params
        
        #Provide a hook to do stuff before validation 
        if hasattr(validation, '_before_validate'):
            validation._before_validate(action, params) #Should we pass self (e.g. controller?)
        
        #Initialize new_params -- if it never gets updated just return params
        new_params = {}
        
        # The validator may be a dictionary, a FormEncode Schema object, or any
        # object with a "validate" method or a Django form.
        if isinstance(validation.validators, djangoforms.ModelForm):
            data = ItemForm(data=params)
            if data.is_valid():
                print "<data.is_valid"
                print "data.clean_data", data.clean_data
                print "data.errors", data.errors
                print "data.data", data.data
            
                new_params=self.clean_data
                
            else:
                print "<NOT data.is_valid"
                print "data.clean_data", data.clean_data
                print "data.errors", data.errors
                print "data.data", data.data
                    
                errors=data.errors
                raise formencode.api.Invalid(
                    formencode.schema.format_compound_error(errors),
                    params, None, error_dict=errors)
                
        
        if isinstance(validation.validators, dict):
            raise "NotImplemented"
            # Play developers can pass in a dict of param names and FormEncode
            # validators.  They are applied one by one and builds up a new set
            # of validated params.

            errors = {}
            for field, validator in validation.validators.iteritems():
                try:
                    validator.to_python(params.get(field))
                    new_params[field] = validator.to_python(params.get(field))
                # catch individual validation errors into the errors dictionary
                except formencode.api.Invalid, inv:
                    errors[field] = inv

            # Parameters that don't have validators are returned verbatim
            for param, param_value in params.items():
                if not param in new_params:
                    new_params[param] = param_value        

            # If there are errors, create a compound validation error based on
            # the errors dictionary, and raise it as an exception
            if errors:
                raise formencode.api.Invalid(
                    formencode.schema.format_compound_error(errors),
                    params, None, error_dict=errors)
            
        elif isinstance(validation.validators, formencode.Schema):
            raise "NotImplemented"
            # A FormEncode Schema object - to_python converts the incoming
            # parameters to sanitized Python values
            new_params = validation.validators.to_python(params)
            
        elif hasattr(validation.validators, 'validate'):
            raise "NotImplemented"
            # An object with a "validate" method - call it with the parameters
            new_params = validation.validators.validate(params)
        
        # Theoretically this should not happen...
        if new_params is None:
            return params
            
        return new_params

    def _render_response(self, controller, response):
        """
        Render response takes the dictionary returned by the
        controller calls the appropriate template engine. It uses
        information off of the decoration object to decide which engine
        and template to use, and removes anything in the exclude_names
        list from the returned dictionary.

        The exclude_names functionality allows you to pass variables to
        some template rendering engines, but not others. This behavior
        is particularly useful for rendering engines like JSON or other
        "web service" style engines which don't use and explicit
        template.

        All of these values are populated into the context object by the
        expose decorator.
        """

        content_type, engine_name, template_name, exclude_names = \
            controller.decoration.lookup_template_engine(pylons.request)

        # Always set content type
        pylons.response.headers['Content-Type'] = content_type 
        req = pylons.request

        if template_name is None:
            return response

        # Deprecation warnings if people return a widget in the dict rather
        # than setting it on tmpl_context.w
        if isinstance(response, dict):
            for key, item in response.iteritems():
                if isinstance(item, Widget):
                    msg = "Returning a widget is deprecated, set them on pylons.widgets instead"
                    warnings.warn(msg, DeprecationWarning)
                    setattr(pylons.c.w, key, item)
        
        # Prepare the engine, if it's not already been prepared.
        if engine_name not in _configured_engines():
            from pylons import config
            template_options = dict(config).get('buffet.template_options', {})
            pylons.buffet.prepare(engine_name, **template_options)
            _configured_engines().add(engine_name)

        # Setup the template namespace, removing anything that the user
        # has marked to be excluded.
        namespace = dict(tmpl_context=pylons.tmpl_context)
        namespace.update(response)

        for name in exclude_names:
            namespace.pop(name)

        # If we are in a test request put the namespace where it can be accessed directly
        if req.environ.get('paste.testing'):
            req.environ['paste.testing_variables']['namespace'] = namespace
            req.environ['paste.testing_variables']['template_name'] = template_name
            req.environ['paste.testing_variables']['exclude_names'] = exclude_names

        # Render the result.
        result = pylons.buffet.render(engine_name=engine_name,
                                      template_name=template_name,
                                      include_pylons_variables=False,
                                      namespace=namespace)
        return result


    def _handle_validation_errors(self, controller, remainder, params, exception):
        """
        Sets up pylons.c.form_values and pylons.c.form_errors to assist
        generating a form with given values and the validation failure
        messages.

        The error handler in decoration.validation.error_handler is called. If
        an error_handler isn't given, the original controller is used as the
        error handler instead.
        """

        pylons.c.validation_exception = exception
        pylons.c.form_errors = {} 
        
        # Most Invalid objects come back with a list of errors in the format:
        #"fieldname1: error\nfieldname2: error"

        error_list = exception.__str__().split('\n')

        for error in error_list:
            field_value = error.split(':')

            #if the error has no field associated with it, 
            #return the error as a global form error
            if len(field_value) == 1:
                pylons.c.form_errors['_the_form'] = field_value[0].strip()
                continue

            pylons.c.form_errors[field_value[0]] = field_value[1].strip()
            
        pylons.c.form_values = exception.value

        error_handler = controller.decoration.validation.error_handler
        if error_handler is None:
            error_handler = controller
            output = error_handler(*remainder, **dict(params))
        elif hasattr(error_handler, 'im_self') and error_handler.im_self != controller:
            output = error_handler(*remainder, **dict(params))
        else:
            output = error_handler(controller.im_self, *remainder, **dict(params))

        return error_handler, output
        
    def _initialize_validation_context(self):
        pylons.c.form_errors = {}
        pylons.c.form_values = {}
    


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Routes

class Router:

    def __init__(self, routes):
        Router.current = self
        self.routes = []
        self.actions = {}
        for pattern, action in routes:
            route = Route(pattern, action)
            self.routes.append(route)
            self.actions['%s.%s' % (route.action.im_class.__name__, route.action.__name__)] = route

    def route_for_action(self, action):
        return self.actions.get(action)

    def route(self, request):
        for route in self.routes:
            params = route.match(request)
            if params != None:
                action = route.action
                controller = action.im_class()
                controller.router = self
                controller._route_params = params
                return (controller, action)
        return (None, None)

    def reverse_route(self, func, args):
        for route in self.routes:
            if route.method == 'get' and route.action == func:
                return route.resolve(args)


class Route:

    def __init__(self, pattern, action):
        self.description = str(pattern)
        self.action = action
        self.method = 'all'
        if pattern.index(' '):
            (self.method, self.path) = tuple(pattern.split())
            self.method = self.method.lower()
            pattern = self.path
        p = re.compile(r'\{([a-zA-Z]+)\}')
        pattern = '^' + p.sub(r'(?P<\1>[^/]+)', pattern) + '$'
        self.re = re.compile(pattern)

    def match(self, request):
        if self.method == 'all' or request.method.lower() == self.method:
            result = self.re.match(request.path)
            if result:
                return result.groupdict()

    def resolve(self, args):
        path = self.path
        for arg in args.keys():
            path = re.compile(r'\{%s\}' % arg).sub(str(args[arg]), path)
        return path


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Controller

class Controller:


    def _veryBefore(self):
        pass

    def _veryAfter(self):
        self.session._store(self)
        self.flash._store(self)

    def use_template(self, template):
        self._use_template=template

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Scopes

class Request(webapp.Request):

    def __init__(self, environ):
        webapp.Request.__init__(self, environ)
        self.domain = self.host[:self.host.index(':')] if self.host.index(':') else self.host
        self.root = ''


class Response(webapp.Response):
    pass


class Params:

    def __init__(self, controller):
        self.controller = controller

    def __getitem__(self, key):
        if self.controller._route_params.has_key(key):
            return self.controller._route_params[key]
        arg = self.controller.request.get(key, allow_multiple=True)
        if len(arg) == 1: arg = arg[0]
        if arg != '' and len(arg) == 0: arg = None
        return arg


class Session(UserDict):

    def __init__(self, controller):
        initialdata = {}
        cookie = controller.request.cookies.get('session')
        if cookie:
            try:
                data = cookie[:cookie.index('-')]
                sign = cookie[cookie.index('-')+1:]
                if sign != self._sign(data):
                    raise Exception('Corrupted')
                initialdata = eval(base64.b64decode(data))
            except Exception, e:
                logging.warn('Invalid session : %s' % e)
        UserDict.__init__(self, initialdata)

    def _store(self, controller):
        data = base64.b64encode(str(self))
        sign = self._sign(data)
        controller.response.headers.add_header(
            'Set-Cookie', 'session=%s-%s; path=/;' % (data, sign)
        )

    def clear(self):
        self.data.clear()

    @classmethod
    def _sign(cls, str):
        return hmac.new(SECRET_KEY, str, hashlib.sha1).hexdigest()


class Flash(UserDict):

    def __init__(self, controller):
        self.out = {}
        initialdata = {}
        cookie = controller.request.cookies.get('flash')
        if cookie:
            try:
                data = cookie[:cookie.index('-')]
                sign = cookie[cookie.index('-')+1:]
                if sign != Session._sign(data):
                    raise Exception('Corrupted')
                initialdata = eval(base64.b64decode(data))
            except Exception, e:
                logging.warn('Invalid flash : %s' % e)
        UserDict.__init__(self, initialdata)

    def _store(self, controller):
        data = base64.b64encode(str(self.out))
        sign = Session._sign(data)
        controller.response.headers.add_header(
            'Set-Cookie', 'flash=%s-%s; path=/;' % (data, sign)
        )

    def __setitem__(self, key, item):
        self.data[key] = item
        self.out[key] = item

    def discard(self, key=None):
        if key:
            self.out.remove(key)
        else:
            self.out = {}

    def keep(self, key=None):
        if key:
            self.out[key] = self.get(key)
        else:
            self.out = dict(self)

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Decorators

class Decoration(object):
    """ Simple class to support 'simple registration' type decorators

    The decoration object can be

    """
    def __init__(self):
        self.engines = {}
        self.validation = None
        self.error_handler = None
        self.hooks = dict(before_validate=[],
                          before_call=[],
                          before_render=[],
                          after_render=[])

    def get_decoration(cls, func):
        if not hasattr(func, 'decoration'):
            func.decoration = cls()
        return func.decoration
    get_decoration = classmethod(get_decoration)

    def exposed(self):
        return bool(self.engines)
    expose = property(exposed)

    def run_hooks(self, hook, *l, **kw):
        for func in self.hooks[hook]:
            func(*l, **kw)

    def register_template_engine(self, content_type, engine, template,
                                 exclude_names):
        """Registers an engine on the controller.

        Multiple engines can be registered, but only one engine per
        content_type.  If no content type is specified the engine is
        registered at */* which is the default, and will be used
        whenever no content type is specified.

        exclude_names keeps track of a list of keys which will be
        removed from the controller's dictionary before it is loaded
        into the template.  This allows you to exclude some information
        from JSONification, and other 'automatic' engines which don't
        require a template.
        """

        if content_type is None:
            content_type = '*/*'
        self.engines[content_type] = engine, template, exclude_names

    def lookup_template_engine(self, request):
        """Return the template engine data.

        Provides a convenience method to get the proper engine,
        content_type, template, and exclude_names for a particular
        play_format (which is pulled off of the request)."
        """
        play_format = request.get('play_format')
        if play_format=='json': play_format='application/json'
        if play_format:
            assert '/' in play_format, 'Invalid play_format: must be a MIME type'
            accept_types = play_format
        else:
            accept_types = request.get('accept', '*/*')
        content_type = best_match(self.engines.keys(), accept_types)
        engine, template, exclude_names = self.engines[content_type]

        if 'charset' not in content_type:
            content_type = '%s; charset=utf-8' % content_type

        return content_type, engine, template, exclude_names

    def register_hook(self, hook_name, func):
        """Registers the specified function as a hook.

        We now have four core hooks that can be applied by adding
        decorators: before_validate, before_call, before_render, and
        after_render. register_hook attaches the function to the hook
        which get's called at the apropriate time in the request life
        cycle.)
        """
        self.hooks[hook_name].append(func)



class _hook_decorator(object):
    """SuperClass for all the specific Play hook validators.
    """
    # must be overridden by a particular hook
    hook_name = None

    def __init__(self, hook_func):
        self.hook_func = hook_func

    def __call__(self, func):
        deco = Decoration.get_decoration(func)
        deco.register_hook(self.hook_name, self.hook_func)
        return func


class before_validate(_hook_decorator):
    """A list of callables to be run before validation is performed"""
    hook_name = 'before_validate'


class before_call(_hook_decorator):
    """A list of callables to be run before the controller method is called"""
    hook_name = 'before_call'


class before_render(_hook_decorator):
    """A list of callables to be run before the template is rendered"""
    hook_name = 'before_render'


class after_render(_hook_decorator):
    """A list of callables to be run after the template is rendered.

    Will be run before it is returned returned up the WSGI stack"""

    hook_name = 'after_render'


class expose(object):
    """
    Registers attributes on the decorated function

    :Parameters:
      template
        Assign a template, you could use the syntax 'genshi:template'
        to use different templates.
        The default template engine is genshi.
      content_type
        Assign content type.
        The default content type is 'text/html'.
      exclude_names
        Assign exclude names

    The expose decorator registers a number of attributes on the
    decorated function, but does not actually wrap the function the way
    TurboGears 1.0 style expose decorators did.

    This means that we don't have to play any kind of special tricks to
    maintain the signature of the exposed function.

    The exclude_names parameter is new, and it takes a list of keys that
    ought to be scrubbed from the dictinary before passing it on to the
    rendering engine.  This is particularly usefull for JSON.

    Expose decorator can be stacked like this::

        @expose('json', exclude_names='d')
        @expose('kid:blogtutorial.templates.test_form',
                content_type='text/html')
        def my_exposed_method(self):
            return dict(a=1, b=2, d="username")

    The expose('json') syntax is a special case.  json is a buffet
    rendering engine, but unlike others it does not require a template,
    and expose assumes that it matches content_type='application/json'

    Otherwise expose assumes that the template is for html.  All other
    content_types must be explicitly matched to a template and engine.
    """

    def __init__(self, template='', content_type=None, exclude_names=None):
        if exclude_names is None:
            exclude_names = []
        if template == 'json':
            engine, template = 'json', ''
        elif ':' in template:
            engine, template = template.split(':', 1)
        elif template:
            # Use the default appengine django templates
            engine = 'django'
        else:
            engine, template = None, None
        if content_type is None:
            if engine == 'json':
                content_type = 'application/json'
            else:
                content_type = 'text/html'
        if engine == 'json' and 'tmpl_context' not in exclude_names:
            exclude_names.append('tmpl_context')
        self.engine = engine
        self.template = template
        self.content_type = content_type
        self.exclude_names = exclude_names

    def __call__(self, func):
        deco = Decoration.get_decoration(func)
        deco.register_template_engine(
            self.content_type, self.engine, self.template, self.exclude_names)
        return func


class validate(object):
    """Registers which validators ought to be applied

    If you want to validate the contents of your form,
    you can use the ``@validate()`` decorator to register
    the validators that ought to be called.

    :Parameters:
      validators
        Pass in a dictionary of FormEncode validators.
        The keys should match the form field names.
      error_handler
        Pass in the controller method which should be used
        to handle any form errors
      form
        Pass in a ToscaWidget based form with validators

    The first positional parameter can either be a dictonary of validators,
    a FormEncode schema validator, or a callable which acts like a FormEncode
    validator.

    """
    def __init__(self, validators=None, error_handler=None, form=None):
        if form:
            self.validators = form
        if validators:
            self.validators = validators
        self.error_handler = error_handler

    def _before_validate(self, controller, params):
        pass

    def __call__(self, func):
        deco = Decoration.get_decoration(func)
        deco.validation = self
        return func


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Templates

import django
import django.conf
import django.template

def url_tag(self, context):
    try:
        route = Router.current.route_for_action(self.view_name)
        return route.resolve(self.kwargs)
    except:
        return ''

template._urlnode_render_replacement = url_tag


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Error report

def report_error(type, value, tb, response):
    response.set_status(500)
    response.clear()
    response.headers['Content-Type'] = 'text/html'
    list = []
    path = os.path.abspath('.') + '/'
    for item in traceback.extract_tb(tb):
        if item[0].startswith(path):
            list.append( (item[0][len(path):], item[1], item[3]) )
    t = django.template.Template("""
        <html>
            <head>
                <title>Error</title>
                <style type="text/css">
                    body, html {
                        margin: 0;
                        padding: 0;
                        font-family: sans-serif;
                    }
                    #error {
                        width: 800px;
                        margin: 0 auto;
                        padding: 20px;
                        background: #FFDFDF;
                        border: 1px solid #FF8F8F;
                        border-top: none;
                        border-bottom: none;
                    }
                    #error h1 {
                        color: #c00;
                        margin: 0;
                    }
                    #error h2 {
                        margin: 0 0 2em 0;
                        font-weight: normal;
                        color: #333;
                        font-size: 1.2em;
                    }
                    #error ul {
                        padding: 0;
                        list-style: none;
                    }
                    #error li {
                        margin-bottom: 1em;
                    }
                    #error li div {
                        background: #fff;
                        padding: 3px 0;
                        overflow: hidden;
                        white-space: nowrap;
                    }
                    #error .file {
                        background: #111;
                        color: #fff;
                        padding: 3px;
                        display: inline-block;
                    }
                    #error .line {
                        background: #ccc;
                        padding: 3px;
                    }
                    #error li:first-child .code {
                        color: #c00;
                    }
                    #error li:first-child {
                        font-size: 150%;
                    }
                </style>
            </head>
            <body>
                <div id="error">
                    <h1>{{ error_name }}</h1>
                    <h2>{{ error_description|default:"(no message)" }}</h1>
                    <ul>
                        {% for trace in traceback reversed %}
                            <li>
                                <span class="file">{{ trace.0 }}</span>
                                <div>
                                    <span class="line">{{ trace.1 }}</span>
                                    <span class="code">{{ trace.2 }}</span>
                                </div>
                            </li>
                            {% if forloop.first %}
                                <li>Called by :</li>
                            {% endif %}
                        {% endfor %}
                    </ul>
                </div>
            </body>
        </html>
    """)
    response.out.write(t.render(django.template.Context({
        'error_name': type.__name__ if hasattr(type, '__name__') else str(type),
        'error_description': value,
        'traceback': list
    })))


